package il.technion.cs236369.proxy;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;

import org.apache.http.HttpRequestInterceptor;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;

public class HttpProxy extends AbstractHttpProxy{

//	private SocketFactory sockFactory;
//	private ServerSocketFactory srvSockFactory;
//	private int port;
	private ServerSocket m_serversocket;
	private HttpParams m_params;
	private HttpService m_httpService;

	/**
	 * Constructs the proxy
	 * 
	 * @param sockFactory
	 *            The SocketFactory to be used for creating new sockets for connecting to servers
	 * 
	 * @param srvSockFactory
	 *            The ServerSocketFactory to be used for creating a single ServerSocket for listening for clients
	 *            requests
	 * 
	 * @param port
	 *            The port number to bounded by the ServerSocket
	 * 
	 * @param dbURL
	 *            url of the database (e.g. jdbc:mysql://127.0.0.1:3306/)
	 * @param dbName
	 *            The name of the database (e.g. proxy)
	 * @param tblName
	 *            The name of the table in the database (e.g. cache)
	 * @param dbUsername
	 *            Database's username (e.g. root)
	 * @param dbPassword
	 *            Database's password
	 * @param dbDriver
	 *            Database's driver class name (com.mysql.jdbc.Driver)
	 */

	@Inject
	HttpProxy(SocketFactory sockFactory, 
			ServerSocketFactory srvSockFactory, 
			@Named("httproxy.net.port") int port,
			@Named("httproxy.db.url") String dbURL, 
			@Named("httproxy.db.name") String dbName,
			@Named("httproxy.db.table") String tblName, 
			@Named("httproxy.db.username") String dbUsername,
			@Named("httproxy.db.password") String dbPassword, 
			@Named("httproxy.db.driver") String dbDriver)
				throws ClassNotFoundException
	{
		super(sockFactory, srvSockFactory, port, dbURL, dbName, tblName,
				dbUsername, dbPassword, dbDriver);

		PropertyConfigurator.configure("log4j.properties");

		// Turning off the logger. Turn on for DEBUG
		Logger.getLogger(LogUtils.cache).setLevel(Level.OFF);
		Logger.getLogger(LogUtils.system).setLevel(Level.OFF);
		Logger.getLogger(LogUtils.server).setLevel(Level.OFF);

		// Open connection to DB
		Cache.connect(dbDriver, dbURL, dbUsername, dbPassword, dbName, tblName);

		// Configure log4j
		Logger.getLogger(LogUtils.system).info("Connection set up..\n");
	}

	/**
	 * Create a new bounded server socket using ServerSocketFactory with the given port
	 * 
	 * @throws IOException
	 *             unable to bind the server socket
	 */
	@Override
	public void bind() throws IOException 
	{
		m_serversocket = srvSockFactory.createServerSocket(port);

		// Set up the HTTP request processor
		HttpProcessor requestProcessor = new ImmutableHttpProcessor(new HttpRequestInterceptor[] {
				new RequestContent(), new RequestTargetHost(), new RequestConnControl(), new RequestUserAgent(),
				new RequestExpectContinue() });

		// Set up request handlers
		HttpRequestHandlerRegistry registry = new HttpRequestHandlerRegistry();
		registry.register("*", new HttpProxyRequestHandler(sockFactory));

		// Set up HTTP parameters
		this.m_params = new SyncBasicHttpParams();
		this.m_params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
				.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
				.setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1");

		// Set up the HTTP service
		this.m_httpService = new HttpService(requestProcessor, new DefaultConnectionReuseStrategy(),
				new DefaultHttpResponseFactory(), registry, this.m_params);

	}

	/**
	 * Starts the server loop: listens to client requests and executes them. To create new sockets for connecting to
	 * servers use ONLY SocketFactory.createSocket(String host, int port) where SocketFactory is the one passed to the
	 * constructor.
	 */
	public void start() {
		System.out.println("PROXY: Listening on port " + this.m_serversocket.getLocalPort());
		Logger.getLogger(LogUtils.system).info("PROXY: Listening on port " + this.m_serversocket.getLocalPort() + "\n");

		while (!Thread.interrupted()) {

			DefaultHttpServerConnection clientToProxyConn = null;

			try {
				// Set up HTTP connection
				Socket socket;

				socket = this.m_serversocket.accept();
				clientToProxyConn = new DefaultHttpServerConnection();
				// DEBUG print
				Logger.getLogger(LogUtils.system).info("Incoming connection from " + socket.getInetAddress() + "\n");
				clientToProxyConn.bind(socket, this.m_params);

				// Start worker (single threaded)
				Worker t = new Worker(this.m_httpService, clientToProxyConn);
				t.run();

			} catch (IOException e) {
				Logger.getLogger(LogUtils.system).error(e.getMessage() + "\n", e);
				System.err.println("HttpProxy: error during IO operation");
			} finally {
			}
		}
	}

	public static void main(String[] args) throws Exception {
		// first argument is the path to the "config" file
		Properties props = new Properties();
		props.load(new FileInputStream(args[0]));
		Injector injector = Guice.createInjector(new HttpProxyModule(props));
		HttpProxy proxy = injector.getInstance(HttpProxy.class);
		proxy.bind();
		proxy.start();
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		Cache.closeConnection();
	}
}
